package org.zoomdev.zoom.dao;

import org.zoomdev.zoom.common.expression.Symbol;
import org.zoomdev.zoom.common.utils.LambdaMethodExtractor;
import org.zoomdev.zoom.common.utils.Page;
import org.zoomdev.zoom.dao.executor.BatchResult;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

public interface Ar<T> {

    T get();

    <R> R value(String column, Class<R> type);

    Values values();

    Page<T> page(int page, int pageSize);

    List<T> limit(int position, int limit);

    List<T> find();

    int update(T data);

    int delete(T data);

    int[] update(List<T> list);

    int insert(T data);

    int[] insert(List<T> list);

    int count();

    List<BatchResult> commit();

    Ar<T> batch(boolean batch);

    Ar<T> filter(String columns);

    Ar<T> set(String key, Object value);

    int update();

    int insert();

    default <R> Ar<T> where(FieldFunction<T, R> key, Object value) {
        return where(LambdaMethodExtractor.extraGetterName(key), value);
    }

    /**
     * dao.ar("table").where("id",1).find()=>select * from table where id=?
     *
     * @param key
     * @param value
     * @return
     */
    Ar<T> where(String key, Object value);

    default <R> Ar<T> where(boolean condition, FieldFunction<T, R> key, Object value) {
        if (condition) {
            where(LambdaMethodExtractor.extraGetterName(key), value);
        }
        return this;
    }

    default <R> Ar<T> orWhere(FieldFunction<T, R> key, Object value) {
        return orWhere(LambdaMethodExtractor.extraGetterName(key), value);
    }

    /**
     * where("id",1).orWhere("id",2)=> id=? or id=?
     *
     * @param key
     * @param value
     * @return
     */
    default Ar<T> orWhere(String key, Object value) {
        return orWhere(key, Symbol.EQ, value);
    }

    Ar<T> orWhere(String key, Symbol symbol, Object value);

    default <R> Ar<T> orLike(FieldFunction<T, R> key, SqlBuilder.Like like, Object value) {
        return orLike(LambdaMethodExtractor.extraGetterName(key), like, value);
    }

    Ar<T> orLike(String key, SqlBuilder.Like like, Object value);

    default <R> Ar<T> like(FieldFunction<T, R> key, SqlBuilder.Like like, Object value) {
        return like(LambdaMethodExtractor.extraGetterName(key), like, value);
    }

    /**
     * like("name",Like.MATCH_LEFT,"张")=> name like ? => '张%'
     *
     * @param key
     * @param like
     * @param value
     * @return
     */
    Ar<T> like(String key, SqlBuilder.Like like, Object value);

    Ar<T> notLike(String key, SqlBuilder.Like like, Object value);

    default <R> Ar<T> like(boolean condition, FieldFunction<T, R> key, SqlBuilder.Like like, Object value) {
        if (condition) {
            return like(LambdaMethodExtractor.extraGetterName(key), like, value);
        }
        return this;
    }

    default <E> Ar<T> whereIn(FieldFunction<T, E> key, Collection<E> values) {
        return whereIn(LambdaMethodExtractor.extraGetterName(key), values);
    }

    /**
     * where("id",Arrays.asList(1,2,3))=> where id in (?,?,?)
     *
     * @param key
     * @param values
     * @return
     */
    <E> Ar<T> whereIn(String key, Collection<E> values);

    /**
     * dao.ar("table").where(id!=null,"id",id).find()=>select * from table where id=?
     *
     * @param condition 是否添加条件
     * @param key
     * @param value
     * @return
     */
    default Ar<T> where(boolean condition, String key, Object value) {
        if (condition) {
            return where(key, value);
        }
        return this;
    }

    /**
     * dao.ar("table").where("id",Symbol.GT,1).find()=>select * from table where id>?
     *
     * @param key
     * @param symbol
     * @param value
     * @return
     */
    Ar<T> where(String key, Symbol symbol, Object value);

    /**
     * whereNotNull("name")=> where name is not null
     *
     * @param key
     * @return
     */
    Ar<T> whereNotNull(String key);

    /**
     * whereNull("name")=> where name is null
     *
     * @param key
     * @return
     */
    Ar<T> whereNull(String key);

    /**
     * orderBy("id",Sort.DESC)=> order by id desc
     * orderBy("id",Sort.DESC).orderBy("name",Sort.ASC)=> order by id desc,name asc
     *
     * @param key
     * @param sort
     * @return
     */
    Ar<T> orderBy(String key, SqlBuilder.Sort sort);

    /**
     * groupBy("id")=> group by id
     * groupBy("id,name")=> group by id,name
     *
     * @param key
     * @return
     */
    Ar<T> groupBy(String key);

    /**
     * having("AVG(score)",Symbol.GT,60)=> having AVG(score) > 60
     *
     * @param key
     * @param symbol
     * @param value
     * @return
     */
    Ar<T> having(String key, Symbol symbol, Object value);

    /**
     * join("order","order.id=product.id")=> inner join order on order.id=product.id
     *
     * @param table
     * @param on
     * @return
     */
    default Ar<T> join(String table, String on) {
        return join(table, on, SqlBuilder.INNER);
    }

//    /**
//     * dao.ar("table").where("id",1).
//     *  union("table2")
//     *  .find()=>  (select * from table where id=?) union (select * from table2 where id=?)
//     *
//     * @return
//     */
//    Ar<T> union(String table);
//
//    /**
//     * dao.ar("table").where("id",1)
//     *  .union("table2").find()=>  (select * from table where id=?) union all (select * from table2 where id=?)
//     *
//     */
//    Ar<T> unionAll(String table);

    /**
     * join("order","order.id=product.id","left")=> left join order on order.id=product.id
     *
     * @param table
     * @param on
     * @param type
     * @return
     */
    Ar<T> join(String table, String on, String type);

    /**
     * {@link SqlBuilder.JoinType}
     * join("order","order.id=product.id",JoinType.LEFT)=> left join order on order.id=product.id
     *
     * @param table
     * @param on
     * @param type
     * @return
     */
    default Ar<T> join(String table, String on, SqlBuilder.JoinType type) {
        return join(table, on, type.value());
    }

    /**
     * select("id").select("name")=> select id,name
     *
     * @param select
     * @return
     */
    Ar<T> select(String select);

    /**
     * selectFunc("count(*)").selectFunc("sum(*)")
     *
     * @param field
     * @return
     */
    Ar<T> selectFunc(String field);

    /**
     * select(Array.asList("id","name"))=> select id,name
     *
     * @param select
     * @return
     */
    Ar<T> select(Iterable<String> select);

    T get(Object... args);

    @FunctionalInterface
    interface FieldFunction<T, R> extends Serializable {
        R apply() throws Throwable;
    }
}
